#!/usr/bin/env python
#.awk '$0 ~ /case [0-9]+: .. 3/ { sys.stdout.write($2 }' src/dmidecode.c|tr ':\n' ', '

from pprint import pprint
import os, sys, subprocess, random, tempfile, time
if sys.version_info[0] < 3:
    import commands as subprocess
from getopt import getopt

# Setup temporary sys.path() with our build dir
(sysname, nodename, release, version, machine) = os.uname()
pyver = sys.version[:3]
sys.path.insert(0,'../build/lib.%s-%s-%s' % (sysname.lower(), machine, pyver))

root_user = (os.getuid() == 0 and True or False)

ERROR = False
HELP = False
VERBOSITY = 0
COLOR = False
DUMPS_D = "private"

try:
    opts, args = getopt(
        sys.argv[1:],
        "hcv",
        ["help", "color", "verbose"]
    )
    for o, a in opts:
        if o in ("-v", "--verbose"):
            VERBOSITY += 1
        elif o in ("-c", "--color"):
            COLOR = True
        elif o in ("-h", "--help"):
            HELP = True
except getopt.GetoptError as err:
    # print help information and exit:
    HELP = True
    ERROR = True

if HELP:
    sys.stdout.write("""
Usage: %s [<options>]

    OPTIONS

        [-h|--help]     #. Take a wild guess.
        [-c|--color]    #. Add pretty ANSI colors.
        [-v|--verbose]  #. The more you add, the louder it gets.

    NOTES

        Due to developer laziness, a single verbosity flag does nothing, so if
        you actually want to get some verbosity, add two verbosity flags (-vv)

""" % os.path.basename(sys.argv[0]))
    sys.exit(ERROR and 1 or 0)

def ascii(s, i):
        return (COLOR and "\033[%d;1m%s\033[0m" or "%d%s") % (30+i, str(s))
def black(s):
        return (COLOR and "\033[30;1m%s\033[0m" or "%s")%(str(s))
def red(s):
        return (COLOR and "\033[31;1m%s\033[0m" or "%s")%(str(s))
def green(s):
        return (COLOR and "\033[32;1m%s\033[0m" or "%s")%(str(s))
def yellow(s):
        return (COLOR and "\033[33;1m%s\033[0m" or "%s")%(str(s))
def blue(s):
        return (COLOR and "\033[34;1m%s\033[0m" or "%s")%(str(s))
def magenta(s):
        return (COLOR and "\033[35;1m%s\033[0m" or "%s")%(str(s))
def cyan(s):
        return (COLOR and "\033[36;1m%s\033[0m" or "%s")%(str(s))
def white(s):
        return (COLOR and "\033[37;1m%s\033[0m" or "%s")%(str(s))

DISPATCH = {
    1 : red,
    2 : green,
    3 : yellow,
    4 : blue,
    5 : magenta,
    6 : cyan,
    7 : white,
}

LINE = "%s\n"%(magenta("="*80))

score = {
    "total"   : 0,
    "skipped" : 0,
    "passed"  : 0,
    "warned"  : 0,
    "failed"  : 0,
}

def passed(msg=None, indent=1):
    global score
    score["total"] += 1
    score["passed"] += 1
    vwrite("%s\n"%green("PASS"), 1)
    if msg: vwrite("%s %s %s\n"%("  "*indent, green("P"), msg), 1)

def skipped(msg=None, indent=1):
    global score
    score["total"] += 1
    score["skipped"] += 1
    vwrite("%s\n"%yellow("SKIP"), 1)
    if msg: vwrite("%s %s %s\n"%("  "*indent, yellow("S"), msg), 1)

def warned(msg=None, indent=1):
    global score
    score["total"] += 1
    score["warned"] += 1
    vwrite("%s\n"%yellow("WARN"), 1)
    if msg: vwrite("%s %s %s\n"%("  "*indent, yellow("S"), msg), 1)

def failed(msg=None, indent=1):
    global score
    score["total"] += 1
    score["failed"] += 1
    vwrite("%s\n"%red("FAIL"), 1)
    if msg: vwrite("%s %s %s\n"%("  "*indent, red("F"), msg), 1)

def test(r, msg=None, indent=1, bad=failed):
    if r:
        passed(msg, indent)
        return True
    else:
        bad(msg, indent)
        return False

def vwrite(msg, vLevel=0):
    if vLevel < VERBOSITY:
        sys.stdout.write(msg)
        sys.stdout.flush()

################################################################################

#. Let's ignore warnings from the module for the test units...
err = open('/dev/null', 'a+', 1)
os.dup2(err.fileno(), sys.stderr.fileno())

vwrite(LINE, 1)
vwrite(" * Testing for command line version of dmidecode ...", 1)
dmidecode_bin = True in [
    os.path.exists(
        os.path.join(_, "dmidecode")
    ) for _ in os.getenv("PATH").split(':')
]
test(dmidecode_bin, bad=warned)
if root_user:
    vwrite(" * Running test as root user, all tests will be executed\n", 1)
else:
    vwrite(" * %s\n"%red("Running test as normal user, some tests will be skipped"), 1)

vwrite(" * Creation of temporary files...", 1)
try:
    FH, DUMP = tempfile.mkstemp()
    os.unlink(DUMP)
    os.close(FH)
    passed()
except:
    failed()

vwrite(LINE, 1)
try:
    vwrite(" * Importing module...", 1)
    import libxml2
    import dmidecode
    if not root_user:
        vwrite("\n%s"%cyan("Not running as root, a warning above can be expected..."), 1)
    passed()

    vwrite("   * Version: %s\n"%blue(dmidecode.version), 1)
    vwrite("   * DMI Version String: %s\n"%blue(dmidecode.dmi), 1)

    vwrite(" * Testing that default device is /dev/mem...", 1)
    test(dmidecode.get_dev() == "/dev/mem")

    if root_user:
        vwrite(" * Testing that write-lock will not break on dump()...", 1)
        test(not dmidecode.dump())

    vwrite(" * Testing ability to change device to %s..."%DUMP, 1)
    test(dmidecode.set_dev(DUMP))

    vwrite(" * Testing that device has changed to %s..."%DUMP, 1)
    test(dmidecode.get_dev() == DUMP)

    if root_user and dmidecode.dmi is not None:
        vwrite(" * Testing that write on new file is ok...", 1)
        test(dmidecode.dump())

        vwrite(" * Testing that file was actually written...", 1)
        time.sleep(0.1)
        if test(os.path.exists(DUMP)):
            os.unlink(DUMP)
    else:
        if dmidecode.dmi is None:
            vwrite(
                " * %s\n" % yellow(
                    "Skipped testing dump() function, dmidecode does not have access to DMI data"
                    ), 1)
        else:
            vwrite(
                " * %s\n" % red(
                    "Skip testing API function, missing root privileges: dmidecode.dump()"
                    ), 1)

    types = list(range(0, 42))+list(range(126, 128))
    bad_types = [-1, -1000, 256]
    sections = [
        "bios",
        "system",
        "baseboard",
        "chassis",
        "processor",
        "memory",
        "cache",
        "connector",
        "slot"
    ]
    devices = []
    if os.path.exists(DUMPS_D):
        devices.extend([os.path.join(DUMPS_D, _) for _ in os.listdir(DUMPS_D)])
    else:
        vwrite(" * If you have memory dumps to test, create a directory called `%s' and drop them in there.\n" % DUMPS_D, 1)

    if root_user and dmidecode.dmi is not None:
        devices.append("/dev/mem")
    else:
        if dmidecode.dmi is not None:
            vwrite(" * %s\n"%red("Running test as normal user, will not try to read /dev/mem"), 1)

    try:
        pymap = '../src/pymap.xml'
        vwrite(" * Loading %s for XML->Python dictonary mapping..." % pymap, 1)
        dmidecode.pythonmap(pymap)
        passed()
    except:
        failed()

    random.shuffle(types)
    random.shuffle(devices)
    random.shuffle(sections)

    for dev in devices:
        vwrite(LINE, 1)
        vwrite(" * Testing %s..."%yellow(dev), 1)
        try:
            fH = open(dev, 'r')
            fH.close()
            passed()
            vwrite("   * Testing set_dev/get_dev on %s..."%(yellow(dev)), 1)
            if test(dmidecode.set_dev(dev) and dmidecode.get_dev() == dev):
                i = 0
                for section in sections:
                    i += 1
                    vwrite("   * Testing %s (%d/%d)..."%(cyan(section), i, len(sections)), 1)
                    try:
                        output = getattr(dmidecode, section)()
                        test(output is not False)
                        if output:
                            vwrite("     * %s\n"%black(output.keys()), 1)
                    except LookupError as e:
                        failed(e, 1)

                for i in bad_types:
                    vwrite("   * Testing bad type %s..."%red(i), 1)
                    try:
                        output = dmidecode.type(i)
                        test(output is False)
                    except SystemError:
                        failed()

                for i in types:
                    vwrite("   * Testing type %s..."%red(i), 1)
                    try:
                        output = dmidecode.type(i)
                        if dmidecode_bin:
                            _output = subprocess.getoutput("dmidecode -t %d"%i).strip().split('\n')
                            test(len(_output) == 1 and len(output) == 0 or True)
                        else:
                            test(output is not False)
                        if output:
                            vwrite("     * %s\n"%output.keys(), 1)
                    except IOError as e:
                        failed(e, 1)
                    except LookupError as e:
                        failed(e, 1)


                dmixml = dmidecode.dmidecodeXML()
                try:
                    vwrite("   * XML: Swapping result type dmidecodeXML::SetResultType('-') - invalid type... ", 1)
                    test(not dmixml.SetResultType('-'))
                except TypeError:
                    vwrite("Not working => ", 1)
                    passed()
                except:
                    vwrite("Accepted => ", 1)
                    failed()

                try:
                    vwrite("   * XML: Swapping result type - dmidecodeXML::SetResultType(dmidecode.DMIXML_DOC) - valid type...", 1)
                    test(dmixml.SetResultType(dmidecode.DMIXML_DOC))
                    vwrite("   * XML: Swapping result type - dmidecodeXML::SetResultType(dmidecode.DMIXML_NODE) - valid type...", 1)
                    test(dmixml.SetResultType(dmidecode.DMIXML_NODE))
                except:
                    failed()

                for i in bad_types:
                    vwrite("   * XML: Testing bad type - dmidecodeXML::QueryTypeId(%s)..." % red(i), 1)
                    try:
                        output_node = dmixml.QueryTypeId(i)
                        test(not isinstance(output_node, libxml2.xmlNode))
                    except SystemError:
                        vwrite("Accepted => ", 1)
                        failed()
                    except TypeError:
                        vwrite("Not working => ", 1)
                        passed()
                    except ValueError:
                        vwrite("Not working => ", 1)
                        passed()

                for i in types:
                    vwrite("   * XML: Testing dmidecodeXML::QueryTypeId(%s)..." % red(i), 1)
                    try:
                        output_node = dmixml.QueryTypeId(i)
                        test(isinstance(output_node, libxml2.xmlNode))
                    except Exception as e:
                        failed(e, 1)
                    except:
                        failed()

                dmixml.SetResultType(dmidecode.DMIXML_DOC)
                i = 0
                for section in sections:
                    i += 1
                    vwrite("   * %s (%d/%d)..." % (
                        "XML: Testing dmidecodeXML::QuerySection('%s')" % cyan(
                            section
                        ), i, len(sections)
                    ), 1)
                    try:
                        output_doc = dmixml.QuerySection(section)
                        test(isinstance(output_doc, libxml2.xmlDoc))
                    except Exception as e:
                        failed(e, 1)
                    except:
                        failed()

        except IOError:
            skipped()

except ImportError as err:
    failed()
    print(err)

vwrite(LINE, 1)
vwrite("Devices : %s\n"%cyan(len(devices)), 1)
vwrite("Total   : %s\n"%blue(score["total"]), 1)
vwrite("Skipped : %s\n"%yellow(score["skipped"]), 1)
vwrite("Warned  : %s\n"%yellow(score["warned"]), 1)
vwrite("Passed  : %s\n"%green(score["passed"]), 1)
vwrite("Failed  : %s\n"%red(score["failed"]), 1)

sys.exit(score["failed"] != 0 and 1 or 0)
